using System.CodeDom;
using System.Collections;
using EnvDTE;
using EnvDTE80;
using NUnitGenAddIn.Core;
using VSLangProj;

namespace NUnitGenAddIn.VS2005
{
    /// <summary>
    /// Visual Studio 2005 Unit Test Generator
    /// </summary>
	public class VS2005UnitTestGenerator : UnitTestGenerator
	{
        /// <summary>
        /// Initializes a new instance of the <see cref="VS2005UnitTestGenerator"/> class.
        /// </summary>
        /// <param name="addInHelper">The add in helper.</param>
        /// <param name="unitTestGeneratorHelper">The unit test generator helper.</param>
        /// <param name="codeDomHelper">The code DOM helper.</param>
		public VS2005UnitTestGenerator(AddInHelper addInHelper, UnitTestGeneratorHelper unitTestGeneratorHelper,
		                               CodeDomHelper codeDomHelper)
			: base(addInHelper, unitTestGeneratorHelper, codeDomHelper)
		{}

        /// <summary>
        /// Generates the test method.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <param name="testClass">The test class.</param>
        /// <param name="testMethodName">Name of the test method.</param>
		protected override void GenerateTestMethod(CodeFunction targetFunction, CodeClass testClass, string testMethodName)
		{
			CodeFunction testFunction =
                testClass.AddFunction(testMethodName, vsCMFunction.vsCMFunctionFunction, 
                                      vsCMTypeRef.vsCMTypeRefVoid, -1,vsCMAccess.vsCMAccessPublic, 0);
			testFunction.AddAttribute("Test", "", 0);
            testFunction.DocComment = DocumentationHelper.GetDocumentationHeaderString(testMethodName);            
			_addInHelper.GetActiveDocument().Save("");

			TextPoint functionStartPoint = testFunction.GetStartPoint(vsCMPart.vsCMPartBody);
			EditPoint editPoint = functionStartPoint.CreateEditPoint();
			editPoint.Insert(GenerateTestMethodBodyFromTargetFunction(targetFunction));
			_addInHelper.ExecuteCommand("Edit", "FormatDocument", "");
			_addInHelper.GetActiveDocument().Save("");
			((TextSelection) _addInHelper.GetActiveDocument().Selection).GotoLine(editPoint.Line, false);
		}

        /// <summary>
        /// Adds the new test fixture file.
        /// </summary>
        /// <param name="testClassName">Name of the test class.</param>
        /// <param name="targetClass">The target class.</param>
		protected override void AddNewTestFixtureFile(string testClassName, CodeClass targetClass)
		{
			Project project = targetClass.ProjectItem.ContainingProject;
			const string testFolderName = "Test";
			string testFilename = testClassName + ".cs";
			Solution2 solution = _addInHelper.GetSolution() as Solution2;
            if (solution != null)
            {
                string csharpCodeFileTemplatePath = solution.GetProjectItemTemplate("CodeFile", "CSharp");
                SourceControl scc = _addInHelper.GetSourceControl();
                string itemName = project.FullName;
                if (scc.IsItemUnderSCC(itemName) && !scc.IsItemCheckedOut(itemName))
                {
                    scc.CheckOutItem(itemName);
                }

                string name =  targetClass.Namespace.FullName;

                string[] names = name.Split('.');

                ProjectItem testFolder = null;
                try
                {
                    testFolder = project.ProjectItems.Item(testFolderName);
                }
                catch
                {}

                if (testFolder == null)
                {
                    testFolder = project.ProjectItems.AddFolder(testFolderName, "");
                    ProjectItem folder = null;

                    foreach (string space in names)
                    {
                        try
                        {
                            folder = testFolder.ProjectItems.Item(space);
                        }
                        catch
                        { }

                        testFolder = folder ?? testFolder.ProjectItems.AddFolder(space, "");
                    }
                }
                else
                {
                    ProjectItem folder = null;

                    foreach (string space in names)
                    {
                        try
                        {
                            folder = testFolder.ProjectItems.Item(space);
                        }
                        catch
                        { }

                        testFolder = folder ?? testFolder.ProjectItems.AddFolder(space, "");
                    }
                }

                testFolder.ProjectItems.AddFromTemplate(csharpCodeFileTemplatePath, testFilename);
            }

            VSProject vsProject = project.Object as VSProject;
			try
			{
			    if (vsProject != null)
			    {
			        vsProject.References.Add(_unitTestGeneratorHelper.NUnitFrameworkDllPath);
			    }
			}
			catch
			{}
		}

        /// <summary>
        /// Determines whether if the function is a constructor.
        /// </summary>
        /// <param name="codeFunction">The code function.</param>
        /// <returns>
        /// 	<c>true</c> if the function is a constructor otherwise, <c>false</c>.
        /// </returns>
		protected override bool IsFunctionConstructor(CodeFunction codeFunction)
		{
			return codeFunction.FunctionKind == vsCMFunction.vsCMFunctionConstructor;
		}

        /// <summary>
        /// Generates the test method body statements from target function.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <returns><see cref="CodeStatementCollection"/> object</returns>
		protected override CodeStatementCollection GenerateTestMethodBodyStatementsFromTargetFunction(
			CodeFunction targetFunction)
		{
			string targetTypeName = ((CodeElement) targetFunction.Parent).Name;
			CodeStatementCollection statementCollection = new CodeStatementCollection();
			CodeExpression[] funcParamsArray = PrepareParameters(targetFunction, statementCollection);

			if (targetFunction.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
				ProcessConstructor(targetTypeName, statementCollection, funcParamsArray);
			else
				ProcessOtherMethod(targetFunction, targetTypeName, statementCollection, funcParamsArray);

			statementCollection.Add(_unitTestGeneratorHelper.CreateTestFailMethodInvokeExpression("Create or modify test(s)."));
			return statementCollection;
		}

        /// <summary>
        /// Processes the parameters.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <param name="statementCollection">The statement collection.</param>
		protected override void ProcessParameters(CodeFunction targetFunction, CodeStatementCollection statementCollection)
		{
			foreach (CodeParameter2 param in targetFunction.Parameters)
			{
				if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindOut)
				{
					statementCollection.Add(
						_unitTestGeneratorHelper.CreateIsNotNullMethodInvokeExpression("expected" + param.Name,
						                                                              param.Name + " out parameter should not be null"));
				}
				else if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindRef)
				{
					statementCollection.Add(
						_unitTestGeneratorHelper.CreateAreEqualMethodInvokeExpression("expected" + param.Name, param.Name,
						                                                             param.Name + " ref parameter has unexpected result."));
				}
			}
		}

        /// <summary>
        /// Prepares the parameters.
        /// </summary>
        /// <param name="targetFunction">The target function.</param>
        /// <param name="statementCollection">The statement collection.</param>
        /// <returns></returns>
		protected override CodeExpression[] PrepareParameters(CodeFunction targetFunction,
		                                                      CodeStatementCollection statementCollection)
		{
			CodeExpressionCollection funcParams = new CodeExpressionCollection();
			foreach (CodeParameter2 param in targetFunction.Parameters)
			{
				statementCollection.Add(_codeDomHelper.CreateVariableDeclarationStatement(param.Type, param.Name));
				if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindOut
				    || param.ParameterKind == vsCMParameterKind.vsCMParameterKindRef)
					statementCollection.Add(_codeDomHelper.CreateVariableDeclarationStatement(param.Type, "expected" + param.Name));
				CodeExpression paramVariableExpression = new CodeVariableReferenceExpression(param.Name);
				if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindOut)
					funcParams.Add(new CodeDirectionExpression(FieldDirection.Out, paramVariableExpression));
				else if (param.ParameterKind == vsCMParameterKind.vsCMParameterKindRef)
					funcParams.Add(new CodeDirectionExpression(FieldDirection.Ref, paramVariableExpression));
				else
					funcParams.Add(paramVariableExpression);
			}
			CodeExpression[] funcParamsArray = new CodeExpression[funcParams.Count];
			funcParams.CopyTo(funcParamsArray, 0);
			return funcParamsArray;
		}

        /// <summary>
        /// Gets the type list from code elements.
        /// </summary>
        /// <param name="typeList">The type list.</param>
        /// <param name="codeElements">The code elements.</param>
		protected override void  GetTypeListFromCodeElements(ArrayList typeList, CodeElements codeElements)
		{
			foreach (CodeElement element in codeElements)
			{
				if (element.Kind == vsCMElement.vsCMElementClass)
				{
					CodeClass type = element as CodeClass;
					if (IsTypeEligibleForUnitTestGeneration(type))
						typeList.Add(type);
				}
				if (element.Children.Count > 0)
						GetTypeListFromCodeElements(typeList, element.Children);
			}
		}
	}
}